﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Db4objects.Db4o.Collections;
using Microsoft.Surface.Presentation.Controls;
using ZOIL.DataBackend;
using ZOIL.Framework.Presentation;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace ZOIL.Library.Components
{
    /// <summary>
    /// Interaction logic for VHyperGrid2c.xaml
    /// </summary>
    public partial class VHyperGrid2c : DataGrid
    {
        /// <summary>
        /// datastructure for storing all attributes of all (also differing) objects of the current landscape (respectively the rootcollection) (movies, persons, coutries, emails, ...)
        /// the surrounding dictionary holds all object types (e.g. movies, emails, websites, ...)
        /// the inner hashset holds all the metadata-types (respectively PropertyInfos)
        /// </summary>
        //TODO auslagern in extra klasse des frameworks
        public Dictionary<Type, HashSet<PropertyInfo>> Attributes = new Dictionary<Type, HashSet<PropertyInfo>>();

        /// <summary>
        /// datastructure for storing the attribute bindings for each object-type (ZPhoto, Movie, ...)
        /// </summary>
        private Dictionary<Type, Dictionary<int, ObservableCollection<Binding>>> attributeBindings = new Dictionary<Type, Dictionary<int, ObservableCollection<Binding>>>();

        /// <summary>
        /// datastructure for storing the textual filters
        /// </summary>
        private ObservableCollection<TextBox> filterTextBoxes = new ObservableCollection<TextBox>();

        #region RowHeightZoomed DependencyProperty

        /// <summary>
        /// RowHeightZoomed Dependency Property
        /// </summary>
        public static readonly DependencyProperty RowHeightZoomedProperty =
            DependencyProperty.Register("RowHeightZoomed", typeof(double), typeof(VHyperGrid2),
                new FrameworkPropertyMetadata((double)184));

        /// <summary>
        /// Gets or sets the RowHeightZoomed property.  This dependency property 
        /// indicates the default height of a row when zoomed into.
        /// </summary>
        public double RowHeightZoomed
        {
            get { return (double)GetValue(RowHeightZoomedProperty); }
            set { SetValue(RowHeightZoomedProperty, value); }
        }

        #endregion

        #region zoomInDuration & zoomOutDuration DependencyProperty

        /// <summary>
        /// zoomInDuration Dependency Property
        /// </summary>
        public static readonly DependencyProperty zoomInDurationProperty =
            DependencyProperty.Register("zoomInDuration", typeof(int), typeof(VHyperGrid2c),
                new FrameworkPropertyMetadata((int)100));

        /// <summary>
        /// Gets or sets the zoomInDuration property.  This dependency property 
        /// indicates the time needed for zooming into a row or cell (in milliseconds).
        /// </summary>
        public int zoomInDuration
        {
            get { return (int)GetValue(zoomInDurationProperty); }
            set { SetValue(zoomInDurationProperty, value); }
        }

        /// <summary>
        /// zoomOutDuration Dependency Property
        /// </summary>
        public static readonly DependencyProperty zoomOutDurationProperty =
            DependencyProperty.Register("zoomOutDuration", typeof(int), typeof(VHyperGrid2c),
                new FrameworkPropertyMetadata((int)80));

        /// <summary>
        /// Gets or sets the zoomOutDuration property.  This dependency property 
        /// indicates the time needed for zooming out of a row or cell (in milliseconds).
        /// </summary>
        public int zoomOutDuration
        {
            get { return (int)GetValue(zoomOutDurationProperty); }
            set { SetValue(zoomOutDurationProperty, value); }
        }

        #endregion

        static VHyperGrid2c()
        {
            #region set default values for hypergrid (the ones which differ from datagrid)
            Type ownerType = typeof(VHyperGrid2c);

            //auto generate columns
            AutoGenerateColumnsProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(false));
            ColumnWidthProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(new DataGridLength(1, DataGridLengthUnitType.Star)));

            //scrollbars
            HorizontalScrollBarVisibilityProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ScrollBarVisibility.Hidden));
            VerticalScrollBarVisibilityProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ScrollBarVisibility.Visible));

            //gridlines
            GridLinesVisibilityProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(DataGridGridLinesVisibility.Horizontal));

            //columns
            HeadersVisibilityProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(DataGridHeadersVisibility.All));
            CanUserSortColumnsProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(true));
            CanUserReorderColumnsProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(true));
            CanUserResizeColumnsProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(true));
            ColumnHeaderHeightProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(50.0));
            EnableColumnVirtualizationProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(false));

            //rows
            RowHeightProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(20.0));
            MinRowHeightProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata((double)20.0));
            CanUserAddRowsProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(false));
            CanUserResizeRowsProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(true));
            EnableRowVirtualizationProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(false));

            //alternating row background
            AlternationCountProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(2));
            AlternatingRowBackgroundProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(Brushes.Gray));

            //selection mode
            SelectionModeProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(DataGridSelectionMode.Extended));
           
            #endregion
        }

        public VHyperGrid2c()
        {
            InitializeComponent();
            //set attributes including their parent-type (ZPhoto, Movie, ...) for internal and external use
            setAttributesWithType();

            this.Sorting += HyperGrid_CustomSorting; //HyperGrid2_CustomSorting_new;
            this.Loaded += new RoutedEventHandler(VHyperGrid2c_Loaded);

            this.ItemContainerGenerator.StatusChanged += new EventHandler(ItemContainerGenerator_StatusChanged);
        }

        /// <summary>
        /// helper for setting attributes only after ItemContainerGenerator thread (which fills items into the grid) is finished
        /// </summary>
        /// <param name="sender">HyperGrid itself</param>
        /// <param name="e">ItemContainerGenerator.StatusChanged Event</param>
        void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
        {
            if (this.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
            {
                this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
                Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input, new VoidDelegate(DelayedFillAttributes));
            }
        }

        delegate void VoidDelegate();

        /// <summary>
        /// method for delaying setting of attribute-bindings and sortmemberpaths of columns only after items are present
        /// </summary>
        void DelayedFillAttributes()
        {
            //fill datastructure with attributes of each object-type (movie, zphoto, ...)
            fillAttributeBindings();

            //set the sort-order for all columns
            //TODO: needs to be recalled when assignment changes (e.g. by manually reordering the attributs)
            setSortMemberPathForAllColumns();
        }

        /// <summary>
        /// sets some properties which couldn't be set in static-part of class and some styles/templates
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void VHyperGrid2c_Loaded(object sender, RoutedEventArgs e)
        {
            //refresh Properties which aren't set correctly
            refreshProperties();

            //set styles, templates & events
            setTemplates();
        }

        #region set Attributes, their types and their parent-types
        private void setAttributesWithType()
        {
            IEnumerable<DInformationObject> rc = Database.getInstance().ObjectCache.OfType<DInformationObject>();

            //iterate over all objects in the rootcollection
            foreach (DInformationObject currObj in rc)
            {
                Type type = currObj.GetType();

                //TODO check only for real dataobjects in a more abstract way...
                //object mus not be of UI-Element- or Databackend Type
                if (type != typeof(ZPortal) &&
                    type != typeof(ZPortalShape) &&
                    type != typeof(Notepad) &&
                    type != typeof(ZAnotoDrawingPad)
                    )
                {
                    //if object-type wasnt recognized before - add it to the attribute-collection
                    if (!Attributes.ContainsKey(type))
                    {
                        //object was not found - so add it to the attributes datastructure
                        Attributes.Add(type, new HashSet<PropertyInfo>());

                        //and fill its metadata-types                        
                        PropertyInfo[] propInfos = type.GetProperties();
                        foreach (PropertyInfo prop in propInfos)
                        {
                            //TODO get metadata properties in a more abstract way (DB40-specific properties excluded via hardcoded string-matching)
                            if (prop.Name != "ID"
                                && !prop.Name.Equals("Data")
                                && !prop.Name.Equals("Deleted")
                                && !prop.Name.Equals("IsLocked"))
                            {
                                Attributes[type].Add(prop);
                            }
                        }
                    }
                }
            }
        } 
        #endregion

        #region styling
        private void refreshProperties()
        {
            //dirty hack because ...Property.OverrideMetadata is set in Backend but not refreshed on UserInterface
            this.AlternatingRowBackground = this.AlternatingRowBackground;
            this.RowHeight = this.RowHeight;
            this.MinRowHeight = this.MinRowHeight;
            this.ColumnHeaderHeight = this.ColumnHeaderHeight;
            this.ColumnWidth = this.ColumnWidth;
        }

        //TODO probably on "Initialized" or "OnApplyTemplate"
        private void setTemplates()
        {
            //set ColumnHeaderStyle (if not set already)
            if (this.ColumnHeaderStyle == null)
            {
                Style HGColumnHeaderStyle = (Style)this.FindResource("HGColumnHeaderStyle");
                this.ColumnHeaderStyle = HGColumnHeaderStyle;
            }

            // set RowHeaderTemplate (if not set already)
            //if (this.RowHeaderTemplate == null)
            //{
            //    DataTemplate RowHeaderTemplate = (DataTemplate)this.FindResource("RowHeaderTemplate");
            //    this.RowHeaderTemplate = RowHeaderTemplate;
            //}
        } 
        #endregion

        #region filtering
        #region filter event handling
        private void Filter_TextBox_Loaded(object sender, RoutedEventArgs e)
        {
            //TODO: dont do it everytime the text changes...
            fillFilterTextBoxesCollection();
        }

        private void Filter_TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            ICollectionView myCVS = CollectionViewSource.GetDefaultView(this.ItemsSource);

            myCVS.Filter = delegate(object row)
            {
                return FilterRow(row);
            };
        }

        private bool FilterRow(object sender)
        {
            //TODO: make it faster
            DInformationObject currObject = (DInformationObject)sender;
            bool oneFilterMatches = false;
            int numberOfFiltersEmpty = 0;
            for (int i = 0; i < filterTextBoxes.Count; i++)
            {
                TextBox currentColFilter = filterTextBoxes[i];

                if (currentColFilter.Text.Length == 0)
                {
                    numberOfFiltersEmpty++;
                }
                else
                {
                    // get the filter-text
                    String currFilterText = currentColFilter.Text;

                    // get the attributes to search for
                    Type currentObjectType = currObject.GetType();
                    ObservableCollection<Binding> currentAttributes = attributeBindings[currentObjectType].ElementAt(i).Value;

                    Type columnType = this.Columns[i].GetType();

                    // search for filtertext in the corresponding attributes
                    foreach (Binding currBinding in currentAttributes)
                    {
                        String currValue = getFullStringOfAttribute(currObject, getFullAttributeName(currentObjectType, currBinding.Path.Path));
                        if (currValue.ToLower().Contains(currFilterText.ToLower()))
                        {
                            oneFilterMatches = true;
                            break;
                        }
                    }

                    //speed-up enhancements...
                    if (oneFilterMatches)
                    {
                        break;
                    }
                }
            }
            if (filterTextBoxes.Count == numberOfFiltersEmpty)
            {
                return true;
            }
            else
            {
                return oneFilterMatches;
            }
        }

        private string getFullStringOfAttribute(DInformationObject currObject, string currAtt)
        {
            object objectAttValue = currObject.GetValue(currAtt);
            if (objectAttValue == null)
            {
                return "";
            }

            Type attributeType = objectAttValue.GetType();

            //return type-dependant string
            if (attributeType == typeof(String))
            {
                return (string)objectAttValue;
            }
            else if (attributeType == typeof(Double) || attributeType == typeof(float))
            {
                return objectAttValue.ToString();
            }
            else if (attributeType == typeof(int?) || attributeType == typeof(int))
            {
                return objectAttValue.ToString();
            }
            else if (attributeType == typeof(DateTime?) || attributeType == typeof(DateTime))
            {
                DateTime value = (DateTime)objectAttValue;
                return value.ToString();
            }
            else if (attributeType == typeof(Uri))
            {
                Uri value = (Uri)objectAttValue;
                return value.AbsoluteUri.ToString();
            }
            else if (attributeType == typeof(IList<String>))
            {
                IList<String> value = (IList<String>)objectAttValue;

                string returnValue = "";
                foreach (String currVal in value)
                {
                    returnValue += currVal;
                }

                return returnValue;
            }
            else if (attributeType == typeof(ArrayList4<String>))
            {
                ArrayList4<String> value = (ArrayList4<String>)objectAttValue;

                string returnValue = "";
                foreach (String currVal in value)
                {
                    returnValue += currVal;
                }

                return returnValue;
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        private void Filter_Textbox_Reset(object sender, RoutedEventArgs e)
        {
            //get textbox from column where filter-text should be removed
            FrameworkElement filterButton = (FrameworkElement)sender;
            DataGridColumnHeader myGVCH = ZTreeHelper.TryFindParent<DataGridColumnHeader>(filterButton);

            TextBox filterTextBox = ZTreeHelper.TryFindChild<TextBox>(myGVCH);
            filterTextBox.Text = "";
        } 
        #endregion

        #region filter helpers (get the Binding of all Attributes in all columns)

        private void fillFilterTextBoxesCollection()
        {
            //very simple method to get all textfilters (by any keypress in any of the textboxes - just delete the datastructure and rebuild it from scratch)
            filterTextBoxes.Clear();
            DataGridCellsPanel HGCellsPanel = ZTreeHelper.TryFindChild<DataGridCellsPanel>(this);

            //get all HG-column-headers
            foreach (DataGridColumnHeader currentColumnHeader in HGCellsPanel.Children)
            {
                //get their filter textbox
                TextBox currentFilterTextbox = ZTreeHelper.TryFindChild<TextBox>(currentColumnHeader);

                //add filterbox to datastructure
                filterTextBoxes.Add(currentFilterTextbox);
            }
        }

        /// <summary>
        /// fills the attributeBindings datastructure with type-specific (zphoto, movie, ...) bindings of each column
        /// </summary>
        //TODO not really used yet
        private void fillAttributeBindings()
        {
            //iterate over all items and get their type
            for (int rowID = 0; rowID < this.Items.Count; rowID++)
            {
                Type rowType = this.Items[rowID].GetType();

                //add type if it is not contained in attributeBindings
                if (!attributeBindings.Keys.Contains(rowType))
                {
                    attributeBindings.Add(rowType, getAttributesFromCellTemplate(rowID));
                }
            }
        }
        
        /// <summary>
        /// gets the bindings for all cells of a row
        /// </summary>
        /// <param name="rowId">ID of the row to get the Bindings for</param>
        /// <returns>Dictionary with columnId and Collection of all Bindings for each column</returns>
        public Dictionary<int, ObservableCollection<Binding>> getAttributesFromCellTemplate(int rowId)
        {
            Dictionary<int, ObservableCollection<Binding>> columnBindings = new Dictionary<int, ObservableCollection<Binding>>();

            //get row and its columns to search for databound elements
            DataGridRow dataGridRow = (DataGridRow)this.ItemContainerGenerator.ContainerFromItem(this.Items[rowId]);
            DataGridCellsPanel dataGridRowColumns = ZTreeHelper.TryFindChild<DataGridCellsPanel>(dataGridRow);

            //iterate over all columns
            for (int columnId = 0; columnId < dataGridRowColumns.Children.Count; columnId++)
            {
                ObservableCollection<Binding> currColumnBindings = new ObservableCollection<Binding>();

                //get current column for type specific operations
                DataGridColumn column = this.Columns[columnId];
                Type columnType = column.GetType();
                
                //get current cell
                FrameworkElement currCell = (FrameworkElement)dataGridRowColumns.Children[columnId];

                //get bound items for this frameworkelement recursively (iterate over whole childtree)
                getAllBindings(currColumnBindings, currCell);
                
                //add bindings of current column to datastructure
                columnBindings.Add(columnId, currColumnBindings);
            }

            return columnBindings;
        }

        /// <summary>
        /// recursively gets all bindings of a frameworkelement and its children
        /// </summary>
        /// <param name="columnAttributes">datastrurcture to store bindings</param>
        /// <param name="element">frameworkelement to search for a binding (also searched in its children)</param>
        private void getAllBindings(ObservableCollection<Binding> columnAttributes, FrameworkElement element)
        {
            //see: http://blogs.msdn.com/marlat/archive/2009/05/24/getbindingexpression-a-good-way-to-iterate-over-all-dependency-properties-in-visual-tree-in-silverlight-3.aspx
            FieldInfo[] infos = element.GetType().GetFields(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static);

            foreach (FieldInfo field in infos)
            {
                if (field.FieldType == typeof(DependencyProperty))
                {
                    DependencyProperty dp = (DependencyProperty)field.GetValue(null);
                    BindingExpression ex = element.GetBindingExpression(dp);

                    if (ex != null && ex.ParentBinding.Path.Path.Length > 0)
                    {
                        columnAttributes.Add(ex.ParentBinding);
                    }
                }
            }

            int children = VisualTreeHelper.GetChildrenCount(element);

            for (int i = 0; i < children; i++)
            {
                FrameworkElement child = VisualTreeHelper.GetChild(element, i) as FrameworkElement;

                if (child != null)
                {
                    getAllBindings(columnAttributes, child);
                }
            }
        }
        #endregion
        #endregion

        #region sorting
        #region custom sorting
        /// <summary>
        /// custom sorting algotrithm to improve performance and enable sorting of special-types (e.g. Uri, IList(String), ...)
        /// but performance is not very fast - other algorithm with delegate (HyperGrid2_CustomSorting_new) is testet
        /// (gets datasource & column, sets sorting direction & sorter)
        /// </summary>
        /// <param name="sender">HyperGrid ItemsSource</param>
        /// <param name="e">Column which shall be sorted</param>
        private void HyperGrid_CustomSorting(object sender, DataGridSortingEventArgs e)
        {
            //see http://yogesh.jagotagroup.com/blog/post/2008/11/01/Filtering-WPF-toolkit-DataGrid.aspx

            e.Handled = true; // ignore standard-sorting behaviour

            DataGridColumn currentCol = e.Column;
            ListSortDirection direction = (currentCol.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending;
            currentCol.SortDirection = direction;

            ListCollectionView lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(this.ItemsSource);
            CustomSorter mySort = new CustomSorter(direction, currentCol);
            lcv.CustomSort = mySort;
        }

        /// <summary>
        /// custom comparer, not very fast because of large if/else part (couldn't use switch because typeof isn't supported by switch)
        /// </summary>
        public class CustomSorter : IComparer
        {
            public CustomSorter(ListSortDirection direction, DataGridColumn column)
            {
                Direction = direction;
                Column = column;
            }

            public ListSortDirection Direction
            {
                get;
                private set;
            }

            public DataGridColumn Column
            {
                get;
                private set;
            }

            int IComparer.Compare(object X, object Y)
            {
                //TODO how to sort heterogenous data?
                //TODO parsing of special datatypes (e.g. Date, url, mpaa-certificate (order by age of admittance), Color, Images, ...)
                
                //TODO currently very dirty because of type-problems (normally Column.SortMemberPath should be enough, but parent type is somehow needed by DinformationObject.GetValue())
                string sortingCriterium = getFullAttributeName(X.GetType(), Column.SortMemberPath);

                #region michi-tipp via reflection
                //object value1 = null;
                //object value2 = null;
                
                //try
                //{
                //    DInformationObject xObj = (DInformationObject)X;
                //    DInformationObject yObj = (DInformationObject)Y;

                //    PropertyInfo[] properties = DInformationObject.GetProperties(xObj);

                //    PropertyInfo pi = properties.Where(prop => prop.Name == Column.SortMemberPath).Single();

                //    value1 = pi.GetValue(X, null);
                //    value2 = pi.GetValue(Y, null);
                //}
                //catch(Exception e)
                //{

                //}
                #endregion

                object value1 = ((DInformationObject)X).GetValue(sortingCriterium);
                object value2 = ((DInformationObject)Y).GetValue(sortingCriterium);

                int dir = (Direction == ListSortDirection.Ascending) ? 1 : -1;

                #region handling of null values (performance hack)
                if (value1 == null && value2 == null)
                {
                    return 0;
                }
                else if (value1 == null)
                {
                    return 1 * dir;
                }
                else if (value2 == null)
                {
                    return -1 * dir;
                } 
                #endregion

                #region start type specific compare
                Type attributeType = value1.GetType();
                if (attributeType == typeof(String))
                {
                    return StringCompare((String)value1, (String)value2) * dir;
                }
                else if (attributeType == typeof(Double))
                {
                    return DoubleCompare((Double)value1, (Double)value2) * dir;
                }
                else if (attributeType == typeof(int?))
                {
                    return IntCompare((int?)value1, (int?)value2) * dir;
                }
                else if (attributeType == typeof(int))
                {
                    return IntCompare((int)value1, (int)value2) * dir;
                }
                else if (attributeType == typeof(DateTime?))
                {
                    return DateTimeCompare((DateTime?)value1, (DateTime?)value2) * dir;
                }
                else if (attributeType == typeof(DateTime))
                {
                    return DateTimeCompare((DateTime)value1, (DateTime)value2) * dir;
                }
                else if (attributeType == typeof(Uri))
                {
                    return UriCompare((Uri)value1, (Uri)value2) * dir;
                }
                else if (attributeType == typeof(IList<String>))
                {
                    StringBuilder iList1String = new StringBuilder();
                    StringBuilder iList2String = new StringBuilder();

                    foreach (String currOcc in ((IList<String>)value1))
                    {
                        iList1String.Append(currOcc);
                        //speed up by breaking after 50characters (sorting makes no sense anymore - in 99.99% of the cases)...
                        if (iList1String.Length > 50) break;
                    }
                    foreach (String currOcc in ((IList<String>)value2))
                    {
                        iList2String.Append(currOcc);
                        //speed up by breaking after 50characters (sorting makes no sense anymore - in 99.99% of the cases)...
                        if (iList2String.Length > 50) break;
                    }
                    return StringCompare(iList1String.ToString(), iList2String.ToString()) * dir;
                }
                else if (attributeType == typeof(ArrayList4<String>))
                {
                    String arrayList1String = "";
                    String arrayList2String = "";

                    foreach (String currOcc in ((ArrayList4<String>)value1))
                    {
                        arrayList1String += currOcc;
                        //speed up by breaking after 50characters (sorting makes no sense anymore)...
                        if (arrayList1String.Length > 50) break;
                    }
                    foreach (String currOcc in ((ArrayList4<String>)value2))
                    {
                        arrayList2String += currOcc;
                        //speed up by breaking after 50characters (sorting makes no sense anymore)...
                        if (arrayList2String.Length > 50) break;
                    }
                    return StringCompare(arrayList1String, arrayList2String) * dir;
                }
                else
                {
                    throw new NotImplementedException();
                }
                #endregion
            }

            #region Comparer helper classes
            private int StringCompare(string s1, string s2)
            {
                return s1.CompareTo(s2);
            }

            private int DoubleCompare(double? doub1, double? doub2)
            {
                return doub1.Value.CompareTo(doub2.Value);
            }

            private int IntCompare(int? int1, int? int2)
            {
                return int1.Value.CompareTo(int2);
            }

            private int BoolCompare(bool bool1, bool bool2)
            {
                return bool1.CompareTo(bool2);
            }

            private int DateTimeCompare(DateTime? date1, DateTime? date2)
            {
                return date1.Value.CompareTo(date2.Value);
            }

            private int UriCompare(Uri uri1, Uri uri2)
            {
                return uri1.ToString().CompareTo(uri2.ToString());
            }

            #endregion
        }
        #endregion

        #region custom sorting new
        //found on: http://blogs.msdn.com/jgoldb/archive/2008/08/28/improving-microsoft-datagrid-ctp-sorting-performance-part-2.aspx
        private void HyperGrid2_CustomSorting_new(object sender, DataGridSortingEventArgs e)
        {
            ListSortDirection direction = (e.Column.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending;
            e.Column.SortDirection = direction;

            ListCollectionView lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(this.ItemsSource);
            MySort mySort = new MySort(direction, e.Column);
            lcv.CustomSort = mySort;
        }

        /// <summary>
        /// new sorting algorithm with much higher performance - but needs to be specific to the attributes (matches via name)
        /// so - old sorting algo is used for now
        /// TODO: implement matching via type
        /// </summary>
        public class MySort : IComparer
        {
            public delegate int Comparer(ZPhoto arg1, ZPhoto arg2);
            Comparer myCompare;
            public MySort(ListSortDirection direction, DataGridColumn column)
            {
                int dir = (direction == ListSortDirection.Ascending) ? 1 : -1;
                //set a delegate to be called by IComparer.Compare

                switch ((string)column.SortMemberPath)
                {
                    case "BuddyIconURL":
                        myCompare = (a, b) => { return a.BuddyIconURL.AbsolutePath.CompareTo(b.BuddyIconURL.AbsolutePath) * dir; };
                        break;
                    case "UserName":
                        myCompare = (a, b) => { return a.UserName.CompareTo(b.UserName) * dir; };
                        break;
                    //case "Position":
                    //    myCompare = (a, b) => { return a.Position.CompareTo(b.Position) * dir; };
                    //    break;
                    //case "Tel":
                    //    myCompare = (a, b) => { return a.Telephone.CompareTo(b.Telephone) * dir; };
                    //    break;
                    //case "Email":
                    //    myCompare = (a, b) => { return a.Email.CompareTo(b.Email) * dir; };
                    //    break;
                    //case "Enabled":
                    //    myCompare = (a, b) => { return a.Enabled.CompareTo(b.Enabled) * dir; };
                    //    break;
                    //case "City":
                    //    myCompare = (a, b) => { return a.City.CompareTo(b.City) * dir; };
                    //    break;
                    //case "Country":
                    //    myCompare = (a, b) => { return a.Country.CompareTo(b.Country) * dir; };
                    //    break;
                    default:
                        myCompare = (a, b) => { return 0; };
                        break;
                }
            }

            int IComparer.Compare(object X, object Y)
            {
                return myCompare((ZPhoto)X, (ZPhoto)Y);
            }
        }

        #endregion

        #region sorting helpers
        private void setSortMemberPathForAllColumns()
        {
            //TODO is now set via XAML - but needs to be set to topmost attribute automatically (if not set manually)
            //TODO: set sortdirection for all types of objects in rootcollection

            //set sorting for each column
            Dictionary<int,ObservableCollection<Binding>> attributesForCurrentObject = attributeBindings.ElementAt(0).Value;

            for (int i = 0; i < this.Columns.Count(); i++)
            {
                Binding topMostBinding = attributesForCurrentObject.ElementAt(i).Value.ElementAt(0);
                this.Columns[i].SortMemberPath = topMostBinding.Path.Path;
            }
        }
        #endregion
        #endregion

        #region grouping

        #endregion

        #region zooming
        private void rowZoomInto(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            //DataGridRow currentRow = (DataGridRow)sender;
            //DataGridCellsPresenter currentCellsPresenter = ZTreeHelper.TryFindChild<DataGridCellsPresenter>(currentRow);

            DataGridCellsPresenter currentCellsPresenter = (DataGridCellsPresenter)sender;

            // change cell-template to fully zoomed hypergrid cell
            // OR set remaining childrens visibility to visible

            //look if row was already zoomed
            if (currentCellsPresenter.ActualHeight < this.RowHeightZoomed)
            {
                startRowZoomAnimation((FrameworkElement)currentCellsPresenter, currentCellsPresenter.ActualHeight, this.RowHeightZoomed, zoomInDuration);
            }
        }

        private void surface_rowZoom(object sender, System.Windows.Input.TouchEventArgs e)
        {
            //DataGridRow currentRow = (DataGridRow)sender;
            //DataGridCellsPresenter currentCellsPresenter = ZTreeHelper.TryFindChild<DataGridCellsPresenter>(currentRow);

            DataGridCellsPresenter currentCellsPresenter = (DataGridCellsPresenter)sender;

            // change cell-template to fully zoomed hypergrid cell
            // OR set remaining childrens visibility to visible

            //look if listviewitem was already zoomed
            if (currentCellsPresenter.ActualHeight <= this.RowHeightZoomed)
            {
                startRowZoomAnimation((FrameworkElement)currentCellsPresenter, currentCellsPresenter.ActualHeight, this.RowHeightZoomed, zoomInDuration);
            }
            else
            {
                startRowZoomAnimation((FrameworkElement)currentCellsPresenter, currentCellsPresenter.ActualHeight, this.RowHeight, zoomOutDuration);
            }
        }

        private void rowZoomOut(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            //DataGridRow currentRow = (DataGridRow)sender;
            //DataGridCellsPresenter currentCellsPresenter = ZTreeHelper.TryFindChild<DataGridCellsPresenter>(currentRow);

            DataGridCellsPresenter currentCellsPresenter = (DataGridCellsPresenter)sender;

            //check if the attribute under the cursor is still zoomed - if so - just do nothing...
            HitTestResult htr = VisualTreeHelper.HitTest(this, e.GetPosition(this));

            FrameworkElement elementUnderCursor = (FrameworkElement)htr.VisualHit;

            // change cell-template to single-row-height zoomed hypergrid cell
            // OR set remaining childrens visibility to collapsed

            //look if listviewitem was already zoomed && the attribute under the cursor isnt zoomed too
            //if ((currentRow.ActualHeight > lviHeightUnzoomed && elementUnderCursor.Height <= lviHeightUnzoomed) || elementUnderCursor.Parent == null)
            if (currentCellsPresenter.ActualHeight > this.RowHeight)
            {
                startRowZoomAnimation((FrameworkElement)currentCellsPresenter, currentCellsPresenter.ActualHeight, this.RowHeight, zoomOutDuration);
            }
        }

        private void startRowZoomAnimation(FrameworkElement myFE, double fromHeight, double toHeight, int duration)
        {
            //check if rowheight is smaller than initial rowheight
            if (toHeight < this.RowHeight)
            {
                toHeight = this.RowHeight;
            }

            //set animation properties
            Duration zoomDuration = new Duration(TimeSpan.FromMilliseconds(duration));
            DoubleAnimation zoomHeightAnimation = new DoubleAnimation();
            zoomHeightAnimation.From = fromHeight;
            zoomHeightAnimation.To = toHeight;
            zoomHeightAnimation.Duration = zoomDuration;
            zoomHeightAnimation.FillBehavior = FillBehavior.Stop;

            // start animation
            myFE.BeginAnimation(FrameworkElement.HeightProperty, null);
            myFE.BeginAnimation(FrameworkElement.HeightProperty, zoomHeightAnimation);
            myFE.Height = toHeight;
        }
        #endregion

        #region helpers
        /// <summary>
        /// gets the full name of the attribute
        /// </summary>
        /// <param name="currentObjectType"></param>
        /// <param name="attributeName"></param>
        /// <returns></returns>
        //TODO should not be used - this is just a dirty hack because getting an attribute value via reflection is very slow in performance
        private static string getFullAttributeName(Type currentObjectType, string attributeName)
        {
            string objectTypeText = currentObjectType.ToString();
            objectTypeText = objectTypeText.Split('.').Last();
            objectTypeText = firstCharToLower(objectTypeText);
            string attributeTypeText = firstCharToLower(attributeName);
            return objectTypeText + ":" + attributeTypeText;
        }

        /// <summary>
        /// sets the first character of a string to lowercase
        /// </summary>
        /// <param name="objectTypeText">string to set first character to lowercase</param>
        /// <returns>string with first character set to lowercase</returns>
        private static string firstCharToLower(string objectTypeText)
        {
            if (objectTypeText.Length > 1)
            {
                return ((string)objectTypeText[0].ToString()).ToLower() + objectTypeText.Substring(1);
            }
            else
            {
                return objectTypeText.ToLower();
            }
        }
        #endregion

        #region rowheight zoomslider
        private void RowHeightZoomSlider_Loaded(object sender, RoutedEventArgs e)
        {
            Slider zoomSlider = (Slider)sender;

            //set initial value of the slider
            zoomSlider.Value = this.RowHeight;
            //Binding sliderBinding = new Binding();
            //sliderBinding.Source = this.RowHeight;
            //sliderBinding.Mode = BindingMode.OneWay;
            //zoomSlider.SetBinding(SurfaceSlider.ValueProperty, sliderBinding);

            //set ticks for slider
            DoubleCollection tickMarks = new DoubleCollection();
            tickMarks.Add(this.MinRowHeight);
            tickMarks.Add(this.RowHeight);
            tickMarks.Add(this.RowHeightZoomed);
            zoomSlider.Ticks = tickMarks;
        }
        #endregion

        private void RowHeightZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            Slider zoomSlider = (Slider)sender;

            for (int rowID = 0; rowID < this.Items.Count; rowID++)
            {
                DataGridRow dataGridRow = (DataGridRow)this.ItemContainerGenerator.ContainerFromIndex(rowID);

                if (dataGridRow != null)
                {
                    DataGridCellsPresenter currentCellsPresenter = ZTreeHelper.TryFindChild<DataGridCellsPresenter>(dataGridRow);
                    BindingExpression heightBinding = zoomSlider.GetBindingExpression(SurfaceSlider.ValueProperty);
                    //TODO get correct binding (this one is not the exact same as the binding at the initiation of the hypergrid - dont know how to get it...)
                    this.RowHeight = zoomSlider.Value; //TODO: unsauber
                    currentCellsPresenter.SetBinding(DataGridCellsPresenter.HeightProperty, heightBinding.ParentBinding);
                }
            }
        }
    }
}
